/*
 *  This file is part of LifeBudget - Copyright 2010 Clifton Eaton
 *  ------------------------------------------------------------------
 *  LifeBudget is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  LifeBudget is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with LifeBudget.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.lifebudget.app.persistence.repository;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;

import org.lifebudget.app.client.mvc.model.Category;
import org.lifebudget.app.client.mvc.model.CategoryAmount;
import org.lifebudget.app.client.mvc.model.PaycheckCalculator;
import org.lifebudget.app.misc.UserHelper;
import org.lifebudget.app.persistence.EMF;
import org.lifebudget.app.persistence.dao.CategoryAmountDAO;
import org.lifebudget.app.persistence.dao.PaycheckCalculatorDAO;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

/**
 * The Class PaycheckCalculatorRepository uses Data Access Objects and persists
 * them using JPA.  
 */
public class PaycheckCalculatorRepository {
	
	/** The emf. */
	private EntityManagerFactory emf;
	
	/** The category repo. */
	private CategoryRepository categoryRepo;

	/**
	 * Instantiates a new paycheck calculator repository.
	 */
	public PaycheckCalculatorRepository() {
		this.emf = EMF.get();
		categoryRepo = new CategoryRepository();
	}

	/**
	 * Gets the all paycheck calculators.
	 *
	 * @return the all paycheck calculators
	 */
	public List<PaycheckCalculator> getAllPaycheckCalculators() {
		// return TestData.getPaycheckCalculators();
		List<PaycheckCalculator> paychecks = new ArrayList<PaycheckCalculator>();
		EntityManager em = emf.createEntityManager();
		try {
			Query q = em.createQuery("SELECT c FROM " + PaycheckCalculatorDAO.class.getName()
					+ " c WHERE c.userId = ?1");
			q.setParameter(1, UserHelper.getUser().getUserId());
			List<PaycheckCalculatorDAO> results = q.getResultList();

			for (PaycheckCalculatorDAO p : results) {
				paychecks.add(p.toModelObject());
			}
		} finally {
			em.close();
		}
		for (PaycheckCalculator paycheckCalculator : paychecks) {
			List<CategoryAmount> categoryAmts = getAllRelatedCategoryAmounts(paycheckCalculator.getId());
			paycheckCalculator.setCategoryAmounts(categoryAmts);
		}
		return paychecks;

	}

	/**
	 * Adds the paycheck calculator.
	 *
	 * @param paycheck the paycheck
	 * @return the paycheck calculator
	 */
	public PaycheckCalculator addPaycheckCalculator(PaycheckCalculator paycheck) {
		System.out.println("In Paycheck Repository");
		EntityManager em = emf.createEntityManager();
		try {
			PaycheckCalculatorDAO pc = new PaycheckCalculatorDAO(paycheck);
			pc.setUserId(UserHelper.getUser().getUserId());
			em.persist(pc);
			// return the paycheck updated with the correct values (id)
			em.refresh(pc);
			paycheck = pc.toModelObject();
		} finally {
			em.close();
		}
		// nothing needs to be done with CategoryAmounts because new paycheck
		// calcs don't have them
		return paycheck;
	}

	/**
	 * Delete paycheck calculator.
	 *
	 * @param paycheck the paycheck
	 */
	public void deletePaycheckCalculator(PaycheckCalculator paycheck) {
		System.out.println("deleting paycheck calculator " + paycheck.getName());
		EntityManager em = emf.createEntityManager();
		try {
			Key key = KeyFactory.createKey(PaycheckCalculatorDAO.class.getSimpleName(), paycheck.getId());
			PaycheckCalculatorDAO pc = em.find(PaycheckCalculatorDAO.class, key);
			em.remove(pc);
		} finally {
			em.close();
		}

		// delete all CategoryAmounts
		deleteAllRelatedCategoryAmounts(paycheck);

	}

	/**
	 * Delete all related category amounts.
	 *
	 * @param pc the pc
	 */
	private void deleteAllRelatedCategoryAmounts(PaycheckCalculator pc) {
		EntityManager em = emf.createEntityManager();
		try {
			Query q = em.createQuery("DELETE FROM " + CategoryAmountDAO.class.getName()
					+ " ca WHERE ca.parentPaycheckId = ?1");
			q.setParameter(1, pc.getId());
			int num = q.executeUpdate();
			System.out.println("Deleted " + num + " PersistableCategoryAmounts");
		} finally {
			em.close();
		}
	}

	/**
	 * Gets the all related persistable category amounts.
	 *
	 * @param paycheckCalcId the paycheck calc id
	 * @return the all related persistable category amounts
	 */
	private List<CategoryAmountDAO> getAllRelatedPersistableCategoryAmounts(long paycheckCalcId) {
		List<CategoryAmountDAO> cas = new ArrayList<CategoryAmountDAO>();
		EntityManager em = emf.createEntityManager();
		Query q = em.createQuery("SELECT c FROM " + CategoryAmountDAO.class.getName()
				+ " c WHERE c.parentPaycheckId = ?1");
		q.setParameter(1, paycheckCalcId);
		try {

			cas = (List<CategoryAmountDAO>) q.getResultList();
			for (CategoryAmountDAO pCategoryAmount : cas) {
				System.out.println("************** " + pCategoryAmount.getCategoryId());
			}
		} finally {
			em.close();
		}

		return cas;
	}

	/**
	 * Gets the all related category amounts.
	 *
	 * @param paycheckCalcId the paycheck calc id
	 * @return the all related category amounts
	 */
	public List<CategoryAmount> getAllRelatedCategoryAmounts(long paycheckCalcId) {
		List<CategoryAmount> results = new ArrayList<CategoryAmount>();
		List<CategoryAmountDAO> pCategoryAmounts = getAllRelatedPersistableCategoryAmounts(paycheckCalcId);

		for (CategoryAmountDAO pCategoryAmount : pCategoryAmounts) {
			CategoryAmount categoryAmt = pCategoryAmount.toModelObject();
			System.out.println("Found related CategoryAmount " + categoryAmt.getId());
			Category category = categoryRepo.findCategory(pCategoryAmount.getCategoryId());
			if (category == null) {
				System.err.println("Could not find category for id " + pCategoryAmount.getCategoryId());
				// TODO bubble up the error
			}

			categoryAmt.setCategory(category);
			results.add(categoryAmt);
		}

		return results;
	}

	/**
	 * Delete category amount.
	 *
	 * @param ca the ca
	 */
	public void deleteCategoryAmount(CategoryAmount ca) {
		if (ca.getId() != 0) {
			EntityManager em = emf.createEntityManager();
			try {
				Key key = KeyFactory.createKey(CategoryAmountDAO.class.getSimpleName(), ca.getId());
				CategoryAmountDAO pca = em.find(CategoryAmountDAO.class, key);
				if (pca != null) {
					// remove it
					em.remove(pca);
				}
			} finally {
				em.close();
			}

		}

	}

	/**
	 * Update category amounts.
	 *
	 * @param categoryAmounts the category amounts
	 * @return the list
	 */
	public List<CategoryAmount> updateCategoryAmounts(List<CategoryAmount> categoryAmounts) {
		List<CategoryAmount> updates = new ArrayList<CategoryAmount>();
		for (CategoryAmount categoryAmount : categoryAmounts) {
			if (categoryAmount.getId() == 0) {
				// add a category amount
				long newId = addCategoryAmount(categoryAmount);
				categoryAmount.setId(newId);
			} else {
				updates.add(categoryAmount);
			}
		}
		// now do the updates
		if (!updates.isEmpty()) {
			EntityManager em = emf.createEntityManager();
			try {
				for (CategoryAmount ca : updates) {
					Key key = KeyFactory.createKey(CategoryAmountDAO.class.getSimpleName(), ca.getId());
					CategoryAmountDAO pca = em.find(CategoryAmountDAO.class, key);
					if (pca != null) {
						pca.setFieldsFromModelObject(ca);
					}
				}
			} finally {
				em.close();
			}
		}
		return categoryAmounts;
	}

	/**
	 * Adds the category amount.
	 *
	 * @param categoryAmt the category amt
	 * @return the long
	 */
	private long addCategoryAmount(CategoryAmount categoryAmt) {
		EntityManager em = emf.createEntityManager();
		CategoryAmountDAO pca;
		try {
			pca = new CategoryAmountDAO(categoryAmt);
			pca.setUserId(UserHelper.getUser().getUserId());
			em.persist(pca);
			em.refresh(pca);
		} finally {
			em.close();
		}
		return pca.getKey().getId();
	}

	/**
	 * Update paycheck calculator.
	 *
	 * @param paycheck the paycheck
	 */
	public void updatePaycheckCalculator(PaycheckCalculator paycheck) {
		EntityManager em = emf.createEntityManager();
		try {
			Key key = KeyFactory.createKey(PaycheckCalculatorDAO.class.getSimpleName(), paycheck.getId());
			PaycheckCalculatorDAO ppc = em.find(PaycheckCalculatorDAO.class, key);
			if (ppc != null) {
				ppc.setFieldsFromModelObject(paycheck);
			}
		} finally {
			em.close();
		}

	}

	/**
	 * Gets the paycheck calculator.
	 *
	 * @param id the id
	 * @return the paycheck calculator
	 */
	public PaycheckCalculator getPaycheckCalculator(long id) {
		EntityManager em = emf.createEntityManager();
		PaycheckCalculator pc = null;
		try {
			Key key = KeyFactory.createKey(PaycheckCalculatorDAO.class.getSimpleName(), id);
			PaycheckCalculatorDAO ppc = em.find(PaycheckCalculatorDAO.class, key);
			if (ppc != null) {
				// something may look for a paycheck calc that was deleted
				pc = ppc.toModelObject();
			}
		} finally {
			em.close();
		}
		return pc;
	}

	/**
	 * Delete category amounts for category.
	 *
	 * @param id the id
	 */
	public void deleteCategoryAmountsForCategory(long id) {
		EntityManager em = emf.createEntityManager();
		try {
			Query q = em.createQuery("DELETE FROM " + CategoryAmountDAO.class.getName()
					+ " ca WHERE ca.categoryId = ?1");
			q.setParameter(1, id);
			int num = q.executeUpdate();
			System.out.println("Deleted " + num + " CategoryAmounts for Category " + id);
		} finally {
			em.close();
		}

	}

}
